package cool.webstudy.admin.utils.aspect.logger.operation;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cool.webstudy.admin.exception.BusinessException;
import cool.webstudy.admin.exception.SystemException;
import cool.webstudy.admin.model.dto.user.UserDetailInfoDTO;
import cool.webstudy.admin.utils.UserDetailUtil;
import cool.webstudy.common.constant.CommonConstant;
import cool.webstudy.common.constant.enums.OperationalStatusEnum;
import cool.webstudy.common.model.dto.logger.opl.CreateOperationLogDBDTO;
import cool.webstudy.common.model.dto.logger.opl.CreateOperationLogESDTO;
import cool.webstudy.common.model.po.logger.opl.OperationLoggerPO;
import cool.webstudy.admin.service.OperationLoggerService;
import cool.webstudy.common.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@Aspect
@Component
@Slf4j
public class ApiOperationLogAspect {
    @Autowired
    private OperationLoggerService operationLoggerService;

    /**
     * 以自定义 @ApiOperationLog 注解为切点，凡是添加 @ApiOperationLog 的方法，都会执行环绕中的代码
     */
    @Pointcut("@annotation(cool.webstudy.admin.utils.aspect.logger.operation.ApiOperationLog)")
    public void apiOperationLog() {}

    /**
     * 环绕
     */
    @Around("apiOperationLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            // 请求开始时间
            long startTime = System.currentTimeMillis();

            // MDC
            MDC.put("traceId", UUID.randomUUID().toString());

            // 获取被请求的类和方法
            String className = joinPoint.getTarget().getClass().getSimpleName();
            String methodName = joinPoint.getSignature().getName();

            // 请求入参
            Object[] args = joinPoint.getArgs();
            // 入参转 JSON 字符串
            String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));

            // 获取接口功能描述信息
            String description = getApiOperationLogDescription(joinPoint);

            // 执行切点方法
            Object result;
            OperationLoggerPO operationLoggerPO = new OperationLoggerPO();
            operationLoggerPO.setUnCode(UUIDUtil.getUUID());
            operationLoggerPO.setTraceId(UUIDUtil.getUUID());
            UserDetailInfoDTO userDetailInfoDTO;
            operationLoggerPO.setInput(argsJsonStr);
            operationLoggerPO.setControllerName(className);
            operationLoggerPO.setFunctionName(methodName);
            operationLoggerPO.setOrderId(0);
            operationLoggerPO.setCreateTime(new Date());

            CreateOperationLogDBDTO createOperationLogDBDTO = new CreateOperationLogDBDTO();
            createOperationLogDBDTO.setTraceId(operationLoggerPO.getTraceId());
            createOperationLogDBDTO.setOperator(operationLoggerPO.getOperator());
            createOperationLogDBDTO.setSourceIp(operationLoggerPO.getSourceIp());
            createOperationLogDBDTO.setCreateTime(operationLoggerPO.getCreateTime());
            createOperationLogDBDTO.setOrderId(operationLoggerPO.getOrderId());
            createOperationLogDBDTO.setControllerName(operationLoggerPO.getControllerName());
            createOperationLogDBDTO.setFunctionName(operationLoggerPO.getFunctionName());
            try {
                userDetailInfoDTO =  UserDetailUtil.getUserDetailInfo();
                operationLoggerPO.setOperator(userDetailInfoDTO.getAccount());
                operationLoggerPO.setSourceIp(userDetailInfoDTO.getSourceIP());
            }catch (Exception e) {
                operationLoggerPO.setOperator(CommonConstant.ANONYMOUS_USER_ACCOUNT);
                operationLoggerPO.setSourceIp(CommonConstant.ANONYMOUS_USER_SOURCE_IP);
            }
            createOperationLogDBDTO.setOperator(operationLoggerPO.getOperator());
            createOperationLogDBDTO.setSourceIp(operationLoggerPO.getSourceIp());
            try {
                result = joinPoint.proceed();
            }catch (BusinessException be){
                handleException(operationLoggerPO,createOperationLogDBDTO,startTime,be);
                //记录异常信息之后将异常正常抛出，交由异常拦截器进行处理
                throw be;
            }catch (SystemException se){
                handleException(operationLoggerPO,createOperationLogDBDTO,startTime,se);
                //记录异常信息之后将异常正常抛出，交由异常拦截器进行处理
                throw se;
            }catch (Exception e){
                handleException(operationLoggerPO,createOperationLogDBDTO,startTime,e);
                //记录异常信息之后将异常正常抛出，交由异常拦截器进行处理
                throw e;
            }
            // 执行耗时
            long executionTime = System.currentTimeMillis() - startTime;
            operationLoggerPO.setCostTime(executionTime);
            operationLoggerPO.setOperationalStatus(OperationalStatusEnum.SUCCESS);
            ObjectMapper objectMapper = new ObjectMapper();
            operationLoggerPO.setResult(objectMapper.writeValueAsString(result));
            createOperationLogDBDTO.setOperationalStatus(operationLoggerPO.getOperationalStatus());
            createOperationLogDBDTO.setCostTime(operationLoggerPO.getCostTime());
            operationLoggerService.sendOperationLogToDBMQ(createOperationLogDBDTO);
            //将审计日志记录保存至ES
            CreateOperationLogESDTO createOperationLogESDTO = new CreateOperationLogESDTO();
            createOperationLogESDTO.setTraceId(operationLoggerPO.getTraceId());
            createOperationLogESDTO.setInput(operationLoggerPO.getInput());
            createOperationLogESDTO.setResult(operationLoggerPO.getResult());
            createOperationLogESDTO.setException(operationLoggerPO.getException());
            createOperationLogESDTO.setCreateTime(operationLoggerPO.getCreateTime());
            operationLoggerService.sendOperationLogToESMQ(createOperationLogESDTO);
            return result;
        } finally {
            MDC.clear();
        }
    }
    /**
     * @param operationLoggerPO
     * @param createOperationLogDBDTO 
     * @param startTime 
     * @param e
     * @description: 处理异常情况
     */
    private void handleException(OperationLoggerPO operationLoggerPO,CreateOperationLogDBDTO createOperationLogDBDTO,long startTime,Exception e){
        // 执行耗时
        long executionTime = System.currentTimeMillis() - startTime;
        operationLoggerPO.setCostTime(executionTime);
        operationLoggerPO.setOperationalStatus(OperationalStatusEnum.FAILURE);
        //FIXME 堆栈信息可能会比较多，可能会达到kafka的限制造成消息丢失
        operationLoggerPO.setException(Arrays.toString(e.getStackTrace()));
        createOperationLogDBDTO.setOperationalStatus(operationLoggerPO.getOperationalStatus());
        createOperationLogDBDTO.setCostTime(operationLoggerPO.getCostTime());
        operationLoggerService.sendOperationLogToDBMQ(createOperationLogDBDTO);
        //将审计日志记录保存至ES
        CreateOperationLogESDTO createOperationLogESDTO = new CreateOperationLogESDTO();
        createOperationLogESDTO.setTraceId(operationLoggerPO.getTraceId());
        createOperationLogESDTO.setInput(operationLoggerPO.getInput());
        createOperationLogESDTO.setResult(operationLoggerPO.getResult());
        createOperationLogESDTO.setException(operationLoggerPO.getException());
        createOperationLogESDTO.setCreateTime(operationLoggerPO.getCreateTime());
        operationLoggerService.sendOperationLogToESMQ(createOperationLogESDTO);
    }

    /**
     * 获取注解的描述信息
     * @param joinPoint
     * @return
     */
    private String getApiOperationLogDescription(ProceedingJoinPoint joinPoint) {
        // 1. 从 ProceedingJoinPoint 获取 MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 2. 使用 MethodSignature 获取当前被注解的 Method
        Method method = signature.getMethod();

        // 3. 从 Method 中提取 LogExecution 注解
        ApiOperationLog apiOperationLog = method.getAnnotation(ApiOperationLog.class);

        // 4. 从 LogExecution 注解中获取 description 属性
        return apiOperationLog.description();
    }
    /**
     * 转 JSON 字符串
     * @return
     */
    private Function<Object, String> toJsonStr() {
        ObjectMapper objectMapper = new ObjectMapper();
        return arg -> {
            if (arg instanceof MultipartFile) {
                //如果该参数是文件对象，则返回文件对象的描述信息
                return ((MultipartFile) arg).getName();
            }
            try {
                return objectMapper.writeValueAsString(arg);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        };
    }
}
